/*
 * Created by BAC IT Deustche Bank
 * User: Sergio Vasquez
 * Date: 5/12/2009
 * Time: 12:36 PM
 * 
 * Description:
 * 
 */
using System;
using System.Collections.Generic;
using System.Collections;


namespace IScheduler.Core.Schedule.Plan
{
	/// <summary>
	/// Description of ScheduledPlan.
	/// 
	/// *   *    *    *   *   *    *  command to be executed
	///	-   -    -    -    -    -    -
	///	|    |    |   |     |     |     |
	///	|    |    |   |     |     |     +----- day of week (0 - 6) (Sunday=0)
	///	|    |    |   |     |     +------- month (1 - 12)
	///	|    |    |   |     +--------- day of month (1 - 31)
	///	|    |    |   +----------- hour (0 - 23)
	///	|    |    +------------- min (0 - 59)
	/// |    +---------- second (0 - 59)
	/// +----- millisecond (0 - 59999)
	/// </summary>
   public class SchedulePlan : IEnumerable<DateTime>
    {
        #region Public properties and constructors

        public PatternCollection Milliseconds { get; private set; }
        public PatternCollection Seconds { get; private set; }
        public PatternCollection Minutes { get; private set; }
        public PatternCollection Hours { get; private set; }
        public PatternCollection DaysOfWeek { get; private set; }
        public PatternCollection DaysOfMonth { get; private set; }
        public PatternCollection Months { get; private set; }
        public PatternCollection Year { get; private set; }
        
        public string Name { get; set; }
        public bool BusinessDay { get; set; }
        public int Id { get; set; }
        
        public SchedulePlan()
        {
            this.Milliseconds = new PatternCollection();
            this.Seconds = new PatternCollection();
            this.Minutes = new PatternCollection();
            this.Hours = new PatternCollection();
            this.DaysOfWeek = new PatternCollection();
            this.DaysOfMonth = new PatternCollection();
            this.Months = new PatternCollection();
            this.Year = new PatternCollection();
        }

        #endregion

        #region Pattern Collection
        //TODO: To think where it can be placed.
        public void ConfigurePatternCollection(ICollection<Pattern> col, string patterns)
        {
            if (patterns.Length > 0)
            {
                foreach (string pattern in patterns.Split(','))
                {
                    col.Add(Pattern.Parse(pattern));
                }
            }
        }
        #endregion        
        
        #region Algorithm for computation of new execution DateTime

        public DateTime ComputeNextExecution(DateTime previous, bool firstTime)
        {
            Queue<Field> workList = new Queue<Field>();

            workList.Enqueue(Field.Millisecond);
            workList.Enqueue(Field.Second);
            workList.Enqueue(Field.Minute);
            if (firstTime)
            {
                workList.Enqueue(Field.Hour);
                workList.Enqueue(Field.Day);
                workList.Enqueue(Field.Month);
                workList.Enqueue(Field.Year);
                workList.Enqueue(Field.Holiday);   
            }

            int pMillisecond = previous.Millisecond, pSecond = previous.Second, pMin = previous.Minute, pHour = previous.Hour, pDay = previous.Day - 1, pMonth = previous.Month, pYear = previous.Year;

            while (workList.Count > 0)
            {
                DateTime dt = previous;
                switch (workList.Dequeue())
                {
                    case Field.Millisecond:
                        dt = previous.AddMilliseconds(Milliseconds.ComputeOffset(dt.Millisecond, pMillisecond, 60000, !firstTime));
                        if (dt.Millisecond != previous.Millisecond) workList.Enqueue(Field.Millisecond);
                        firstTime = true;
                        break;
                    case Field.Second:
                        dt = previous.AddSeconds(Seconds.ComputeOffset(dt.Second, pSecond, 60, !firstTime));
                        if (dt.Minute != previous.Minute) workList.Enqueue(Field.Minute);
                        firstTime = true;
                        break;
                    case Field.Minute:
                        dt = previous.AddMinutes(Minutes.ComputeOffset(dt.Minute, pMin, 60, !firstTime));
                        if (dt.Hour != previous.Hour) workList.Enqueue(Field.Hour);
                        firstTime = true;
                        break;
                    case Field.Hour:
                        dt = dt.AddHours(Hours.ComputeOffset(dt.Hour, pHour, 24, false));
                        if (dt.Hour != previous.Hour)
                        {
                            if (dt.Day != previous.Day) workList.Enqueue(Field.Day);
                            dt = dt.AddMinutes(-dt.Minute);
                            pMin = 0;
                            workList.Enqueue(Field.Minute);
                        }
                        break;
                    case Field.Day:
                        if (DaysOfWeek.Count > 0 || DaysOfMonth.Count > 0)
                        {
                            int dow = (DaysOfWeek.Count == 0) ? int.MaxValue : DaysOfWeek.ComputeOffset((int)dt.DayOfWeek, pDay, 7, false);
                            int dom = (DaysOfMonth.Count == 0) ? int.MaxValue : DaysOfMonth.ComputeOffset(dt.Day - 1, pDay, DateTime.DaysInMonth(dt.Year, dt.Month), false);
                            dt = dt.AddDays(dow < dom ? dow : dom);
                            if (dt.Day != previous.Day)
                            {
                                if (dt.Month != previous.Month) workList.Enqueue(Field.Month);
                                dt = dt.AddHours(-dt.Hour).AddMinutes(-dt.Minute);
                                pHour = pMin = 0;
                                workList.Enqueue(Field.Hour);
                                workList.Enqueue(Field.Minute);
                            }
                        }
                        break;
                    case Field.Month:
                        dt = dt.AddMonths(Months.ComputeOffset(dt.Month, pMonth, 12, false));
                        if (dt.Month != previous.Month)
                        {
                            dt = dt.AddDays(-dt.Day).AddHours(-dt.Hour).AddMinutes(-dt.Minute);
                            pDay = pHour = pMin = 0;
                            workList.Enqueue(Field.Day);
                            workList.Enqueue(Field.Hour);
                            workList.Enqueue(Field.Minute);
                        }
                        break;
                    case Field.Year:
                        //TEST: Please give it a test because I'm not sure if it will work.
                        dt = dt.AddYears(Year.ComputeOffset(dt.Year, pYear, 9999, false));
                        if (dt.Year != previous.Year)
                        {
                        	dt = dt.AddMonths(-dt.Month);
                        	pMonth = 0;
                        	workList.Enqueue(Field.Year);
                        }
                       	break; 
                    case Field.Holiday:
                        //Check if it works for holiday
                        if (BusinessDay)
                        {
                        	if (HolidayManager.IsHoliday(dt))
                        	{
                        		dt = dt.AddDays(1);
                        		workList.Enqueue(Field.Month);
                        	}
                        }
                        break;                        
                }
                previous = dt;
            }

            return previous;
        }

        private enum Field { Millisecond, Second, Minute, Hour, Day, Month, Year, Holiday }

        #endregion

        #region IEnumerable and IEnumerable<DateTime> implementation

        public IEnumerator<DateTime> GetEnumerator(DateTime start)
        {
            return new PlanEnumerator(this, start);
        }

        public IEnumerator<DateTime> GetEnumerator()
        {
            return GetEnumerator(DateTime.Now);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator(DateTime.Now);
        }

        private class PlanEnumerator : IEnumerator<DateTime>
        {
            SchedulePlan plan;
            DateTime start, current;

            public PlanEnumerator(SchedulePlan plan, DateTime start)
            {
                this.plan = plan;
                this.start = start;
                this.current = DateTime.MinValue;
            }

            public DateTime Current
            {
                get { return this.current; }
            }

            object IEnumerator.Current
            {
                get { return this.current; }
            }

            public bool MoveNext()
            {
                this.current = (this.current == DateTime.MinValue)
                    ? this.plan.ComputeNextExecution(this.start, true)
                    : this.plan.ComputeNextExecution(this.current, false);
                return true;
            }

            public void Reset()
            {
                this.current = DateTime.MinValue;
            }

            public void Dispose()
            {
            }
        }

        #endregion
    }
}
